package sketch.ounit.fuzz;

import java.lang.reflect.Modifier;
import java.util.List;

import randoop.ArrayDeclaration;
import randoop.ExecutableSequence;
import randoop.Globals;
import randoop.PrimitiveOrStringOrNullDecl;
import randoop.RConstructor;
import randoop.RMethod;
import randoop.StatementKind;
import randoop.Variable;
import randoop.main.GenInputsAbstract;
import randoop.util.PrimitiveTypes;
import randoop.util.Reflection;
import randoop.util.Util;
import sketch.util.Checker;

//a class just for internal use
public class StatementDumperWithOffset {

	public static void dumpStatement(StatementKind statement, Variable newVar,
			List<Variable> inputVars, int offset, StringBuilder sb) {
		if (statement instanceof RMethod) {
			dumpRMethod((RMethod) statement, newVar, inputVars, offset, sb);
		} else if (statement instanceof RConstructor) {
			dumpRConstructor((RConstructor) statement, newVar, inputVars,
					offset, sb);
		} else if (statement instanceof ArrayDeclaration) {
			dumpArrayStatement((ArrayDeclaration) statement, newVar, inputVars,
					offset, sb);
		} else if (statement instanceof PrimitiveOrStringOrNullDecl) {
			dumpPrimitiveDecl((PrimitiveOrStringOrNullDecl) statement, newVar,
					inputVars, offset, sb);
		} else {
			Checker.fail("Wrong type of statement: " + statement.getClass());
		}
	}

	/**
	 * All private methods
	 * */
	private static void dumpRMethod(RMethod rmethod, Variable newVar,
			List<Variable> inputVars, int offset, StringBuilder sb) {

		 boolean isVoid = void.class.equals(rmethod.getMethod().getReturnType());
	     if (!isVoid) {
	      sb.append(Reflection.getCompilableName(rmethod.getMethod().getReturnType()));
	      String cast = "";
	      Variable v = new Variable(newVar.sequence, newVar.index + offset);
	      sb.append(" " + v.getName() + " = " + cast);
	    }
	    String receiverString = rmethod.isStatic() ? null : (new Variable(inputVars.get(0).sequence, inputVars.get(0).index + offset).getName()) ; //inputVars.get(0).getName();
	    rmethod.appendReceiverOrClassForStatics(receiverString, sb);

	    sb.append(".");
	    sb.append(rmethod.getTypeArguments());
	    sb.append(rmethod.getMethod().getName() + "(");

	    int startIndex = (rmethod.isStatic() ? 0 : 1);
	    for (int i = startIndex ; i < inputVars.size() ; i++) {
	      if (i > startIndex)
	        sb.append(", ");

	      // CASTING.
	      // We cast whenever the variable and input types are not identical.
	      // We also cast if input type is a primitive, because Randoop uses
	      // boxed primitives, and need to convert back to primitive.
	      if (PrimitiveTypes.isPrimitive(rmethod.getInputTypes().get(i)) && GenInputsAbstract.long_format) {
	        sb.append("(" + rmethod.getInputTypes().get(i).getName() + ")");
	      } else if (!inputVars.get(i).getType().equals(rmethod.getInputTypes().get(i))) {
	        sb.append("(" + rmethod.getInputTypes().get(i).getCanonicalName() + ")");
	      }

	      // In the short output format, statements like "int x = 3" are not added to a sequence; instead,
	      // the value (e.g. "3") is inserted directly added as arguments to method calls.
	      StatementKind statementCreatingVar = inputVars.get(i).getDeclaringStatement(); 
	      if (!GenInputsAbstract.long_format && canUseShortFormat(statementCreatingVar)) {
	        Object val = ((PrimitiveOrStringOrNullDecl) statementCreatingVar).getValue();
	        sb.append(PrimitiveTypes.toCodeString(val));
	      } else {
	    	//change the offset
	    	Variable v= new Variable(inputVars.get(i).sequence, inputVars.get(i).index + offset);
	        sb.append(v.getName());
	      }
	    }

	    sb.append(");" + Globals.lineSep);
	}

	private static void dumpRConstructor(RConstructor rconst, Variable varName,
			List<Variable> inputVars, int offset, StringBuilder sb) {

	    assert inputVars.size() == rconst.getInputTypes().size();
	    Class<?> declaringClass = rconst.getConstructor().getDeclaringClass();
	    boolean isNonStaticMember = !Modifier.isStatic(declaringClass.getModifiers()) && declaringClass.isMemberClass();
	    assert Util.implies(isNonStaticMember, inputVars.size() > 0);

	    // Note on isNonStaticMember: if a class is a non-static member class, the
	    // runtime signature of the constructor will have an additional argument
	    // (as the first argument) corresponding to the owning object. When printing
	    // it out as source code, we need to treat it as a special case: instead
	    // of printing "new Foo(x,y.z)" we have to print "x.new Foo(y,z)".

	    // TODO the last replace is ugly. There should be a method that does it.
	    String declaringClassStr = Reflection.getCompilableName(declaringClass);

	    //Variable var0 = ;
	    Variable newVarname = new Variable(varName.sequence, varName.index + offset);
	    sb.append(declaringClassStr + " " + newVarname.getName() + " = "
	        + (isNonStaticMember ? (new Variable(inputVars.get(0).sequence, inputVars.get(0).index + offset)) + "." : "")
	        + "new "
	        + (isNonStaticMember ? declaringClass.getSimpleName() : declaringClassStr)
	        + "(");
	    for (int i = (isNonStaticMember ? 1 : 0) ; i < inputVars.size() ; i++) {
	      if (i > (isNonStaticMember ? 1 : 0))
	        sb.append(", ");

	      // We cast whenever the variable and input types are not identical.
	      if (!inputVars.get(i).getType().equals(rconst.getInputTypes().get(i)))
	        sb.append("(" + rconst.getInputTypes().get(i).getCanonicalName() + ")");
	      
	      // In the short output format, statements like "int x = 3" are not added to a sequence; instead,
	      // the value (e.g. "3") is inserted directly added as arguments to method calls.
	      StatementKind statementCreatingVar = inputVars.get(i).getDeclaringStatement(); 
	      if (!GenInputsAbstract.long_format  && canUseShortFormat(statementCreatingVar)) {
	        sb.append(PrimitiveTypes.toCodeString(((PrimitiveOrStringOrNullDecl) statementCreatingVar).getValue()));
	      } else {
	    	Variable v = new Variable(inputVars.get(i).sequence, inputVars.get(i).index + offset);
	        sb.append(v.getName());
	      }
	    }
	    sb.append(");");
	    sb.append(Globals.lineSep);
	}

	private static void dumpArrayStatement(ArrayDeclaration array,
			Variable newVar, List<Variable> inputVars, int offset,
			StringBuilder sb) {
		if (inputVars.size() > array.getLength())
			throw new IllegalArgumentException("Too many arguments:"
					+ inputVars.size() + " capacity:" + array.getLength());
		String declaringClass = array.getElementType().getCanonicalName();
		//change the newVar
		newVar = new Variable(newVar.sequence, newVar.index + offset);
		sb.append(declaringClass + "[] " + newVar.getName() + " = new "
				+ declaringClass + "[] { ");
		for (int i = 0; i < inputVars.size(); i++) {
			if (i > 0)
				sb.append(", ");

			// In the short output format, statements like "int x = 3" are not
			// added to a sequence; instead,
			// the value (e.g. "3") is inserted directly added as arguments to
			// method calls.
			StatementKind statementCreatingVar = inputVars.get(i).getDeclaringStatement();
			if (!GenInputsAbstract.long_format && canUseShortFormat(statementCreatingVar)) {
				sb.append(PrimitiveTypes
						.toCodeString(((PrimitiveOrStringOrNullDecl) statementCreatingVar)
								.getValue()));
			} else {
				//change the variable
				Variable v = new Variable(inputVars.get(i).sequence, inputVars.get(i).index + offset);
				sb.append(v.getName());
			}
		}
		sb.append("};");
		sb.append(Globals.lineSep);
	}

	private static void dumpPrimitiveDecl(PrimitiveOrStringOrNullDecl decl,
			Variable newVar, List<Variable> inputVars, int offset,
			StringBuilder sb) {
		//make the offset here
		newVar = new Variable(newVar.sequence, newVar.index + offset);
		if (decl.getType().isPrimitive()) {
			sb.append(PrimitiveTypes.boxedType(decl.getType()).getName());
			sb.append(" ");
			sb.append(newVar.getName());
			sb.append(" = new ");
			sb.append(PrimitiveTypes.boxedType(decl.getType()).getName());
			sb.append("(");
			sb.append(PrimitiveTypes.toCodeString(decl.getValue()));
			sb.append(");");
			sb.append(Globals.lineSep);
		} else {
			sb.append(Reflection.getCompilableName(decl.getType()));
			sb.append(" ");
			sb.append(newVar.getName());
			sb.append(" = ");
			sb.append(PrimitiveTypes.toCodeString(decl.getValue()));
			sb.append(";");
			sb.append(Globals.lineSep);
		}
	}
	
	//the utility method
	static boolean canUseShortFormat(StatementKind statementCreatingVar) {
	    return (statementCreatingVar instanceof PrimitiveOrStringOrNullDecl
	            // Do not use the short output format if the value is null, because
	            // the variable type may disambiguate among overloaded methods.
	            // (It would be even nicer to use the short output format unless
	            // disambiguation is truly needed.)
	            && ((PrimitiveOrStringOrNullDecl) statementCreatingVar).getValue() != null);
}
}
